package org.jfonia.connect5.intervals;

import org.jfonia.connect5.basics.AbstractValueNode;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.ValueNode;
import java.util.ArrayList;
import java.util.HashMap;
import org.jfonia.connect5.relations.Equal;

/**
 * List of connected intervals:
 * The endpoint of each interval is constrained to be equal to the beginpoint of the next interval.
 * The collection behaves as an interval itself, with a mutable begin and end.
 *
 * @author wijnand.schepens@hogent.be
 */
public class LinkedIntIntervalList implements Interval
{
	MutableValueNode<Integer> begin = new BasicValueNode(0);
	MutableValueNode<Integer> end   = new BasicValueNode(0);
	ValueNode<Integer> difference   = new AbstractValueNode<Integer>()
	{
		public Integer getValue()
		{
			return end.getValue() - begin.getValue();
		}

		public Class<?> getType()
		{
			return Integer.class;
		}
	};

	ArrayList<Interval<Integer>> elements = new ArrayList<Interval<Integer>>(); // n
	HashMap<MutableValueNode<Integer>, Equal<Integer>> relations = new HashMap<MutableValueNode<Integer>, Equal<Integer>>(); // relation[i] is equality i-1,i

	public LinkedIntIntervalList()
	{
		makeLink(0);
	}


	public MutableValueNode<Integer> getBeginNode()
	{
		return begin;
	}

	public MutableValueNode<Integer> getEndNode()
	{
		return end;
	}

	public ValueNode<Integer> getDifferenceNode()
	{
		return difference;
	}

	public Integer getBegin()
	{
		return begin.getValue();
	}

	public Integer getEnd()
	{
		return end.getValue();
	}

	public Integer getDifference()
	{
		return difference.getValue();
	}

	public int size()
	{
		return elements.size();
	}

	public Interval<Integer> add(Interval<Integer> ts)
	{
		return insert(size(), ts);
	}

	public Interval<Integer> insert(int pos, Interval<Integer> val)
	{
		if (pos < 0 || pos > elements.size())
			throw new IndexOutOfBoundsException();
		removeLink(pos);
		elements.add(pos, val);
		makeLink(pos);
		makeLink(pos + 1);
		//assert relations.size() == elements.size() + 1;
		return val;
	}

	public Interval<Integer> remove(int i)
	{
		assert (i >= 0 && i < elements.size());

		removeLink(i);
		removeLink(i + 1);
		Interval<Integer> val = elements.remove(i);
		makeLink(i);
		//assert relations.size() == elements.size() + 1;
		return val;
	}


	protected MutableValueNode<Integer> getRight(int pos)
	{
		return pos < this.size() ? elements.get(pos).getBeginNode() : this.end;
	}

	protected MutableValueNode<Integer> getLeft(int pos)
	{
		return pos > 0 ? elements.get(pos - 1).getEndNode() : this.begin;
	}

	protected void makeLink(int pos)
	{
		assert(pos >= 0 && pos <= size());
		MutableValueNode<Integer> left = getLeft(pos);
		MutableValueNode<Integer> right = getRight(pos);
		relations.put(left, new Equal<Integer>(left, right)); // right is set equal to left
	}

	protected void removeLink(int pos)
	{
		assert(pos >= 0 && pos <= size());
		MutableValueNode<Integer> left = getLeft(pos);
		MutableValueNode<Integer> right = getRight(pos);
                Equal eq = relations.remove(left);
                
                //if(eq != null){
                    eq.switchOff();
                //}		
	}

        public Interval getIntervalFromIndex(int index){
            return elements.get(index);
        }

        public Interval<Integer> getIntervalFromValue(int value){
            if(value >= begin.getValue() && value <= end.getValue()){
                int pos = 0;
                while(pos < elements.size() && elements.get(pos).getBegin() <= value){
                    pos++;
                }
                pos--;
                return elements.get(pos);
            }else{
                throw new IndexOutOfBoundsException("index "+value+", not in ["+begin.getValue()+","+end.getValue()+"]");
            }
        }

        public int indexOf(Interval<Integer> interval){
            return elements.indexOf(interval);
        }

        public void stretch(int newDifference){
            int currentLength = difference.getValue();
            double factor = (double)newDifference/currentLength;
            for(int i = 0; i < elements.size(); i++){
                Interval<Integer> val = elements.get(i);
                int diff = (int)(factor * val.getDifference());
                val.getEndNode().setValue(val.getBegin()+diff);
            }
            getEndNode().setValue(newDifference);
        }
}
